﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using WhiteFlow.Wpf.Common.Extensions;

namespace WhiteFlow.DevTrackingTool.UI.Behaviors
{
    public static class DataGridBehavior
    {
        #region Constants

        public const string EditOnKeyDownPropertyName = "EditOnKeyDown";
        
        public const string CellEditEndingCommandPropertyName = "CellEditEndingCommand";

        public const string RowEditEndingCommandPropertyName = "RowEditEndingCommand";

        #endregion

        #region Fields

        private static DependencyProperty editOnKeyDown;
        
        private static DependencyProperty cellEditEndingCommand;

        private static DependencyProperty rowEditEndingCommand;

        #endregion

        #region DependencyProperties Declaration

        public static readonly DependencyProperty EditOnKeyDownProperty;
        
        public static readonly DependencyProperty CellEditEndingCommandProperty;

        public static readonly DependencyProperty RowEditEndingCommandProperty;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes the <see cref="DataGridBehavior"/> class.
        /// </summary>
        static DataGridBehavior()
        {
            #region DependencyProperties Registration

            EditOnKeyDownProperty = DependencyProperty.RegisterAttached(
                EditOnKeyDownPropertyName,
                typeof(bool),
                typeof(DataGridBehavior),
                new FrameworkPropertyMetadata(false, OnEditOnKeyDownChanged));

            CellEditEndingCommandProperty = DependencyProperty.RegisterAttached(
                CellEditEndingCommandPropertyName,
                typeof(ICommand),
                typeof(DataGridBehavior),
                new FrameworkPropertyMetadata(null, OnCellEditEndingCommandChanged));

            RowEditEndingCommandProperty = DependencyProperty.RegisterAttached(
                RowEditEndingCommandPropertyName,
                typeof(ICommand),
                typeof(DataGridBehavior),
                new FrameworkPropertyMetadata(null, OnRowEditEndingCommandChanged));

            #endregion
        }

        #endregion

        #region Getters & Setters

        /// <summary>
        /// Sets the cell edit ending command.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetCellEditEndingCommand(UIElement obj, ICommand value)
        {
            obj.SetValue(CellEditEndingCommandProperty, value);
        }

        /// <summary>
        /// Gets the cell edit ending command.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static ICommand GetCellEditEndingCommand(UIElement obj)
        {
            return obj.GetValue(CellEditEndingCommandProperty) as ICommand;
        }

        /// <summary>
        /// Sets the row edit ending command.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetRowEditEndingCommand(UIElement obj, ICommand value)
        {
            obj.SetValue(RowEditEndingCommandProperty, value);
        }

        /// <summary>
        /// Gets the row edit ending command.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static ICommand GetRowEditEndingCommand(UIElement obj)
        {
            return obj.GetValue(RowEditEndingCommandProperty) as ICommand;
        }

        /// <summary>
        /// Sets the EditOnKeyDown property value.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">If set to <c>true</c> the cell must enter directly in editing mode on key pressure.</param>
        public static void SetEditOnKeyDown(UIElement obj, bool value)
        {
            obj.SetValue(EditOnKeyDownProperty, value);
        }

        /// <summary>
        /// Gets the EditOnKeyDown property value.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns><c>true</c> if the cell must enter directly in editing mode on key pressure.</returns>
        public static bool GetEditOnKeyDown(UIElement obj)
        {
            return (bool)obj.GetValue(EditOnKeyDownProperty);
        }

        #endregion

        #region DataBinding Callbacks

        /// <summary>
        /// Called when the CellEditEndingCommand property has changed.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private static void OnCellEditEndingCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            cellEditEndingCommand = args.Property;

            if (obj != null)
            {
                DataGrid dataGrid = obj as DataGrid;

                if (dataGrid != null)
                {
                    if (args.OldValue != null && cellEditEndingCommand != null)
                    {
                        dataGrid.CellEditEnding -= ExecuteCellEditEndingCommand;
                    }

                    if (args.NewValue != null && cellEditEndingCommand != null)
                    {
                        dataGrid.CellEditEnding += ExecuteCellEditEndingCommand;
                    }
                }
            }
        }

        /// <summary>
        /// Called when the RowEditEndingCommand property has changed.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private static void OnRowEditEndingCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            rowEditEndingCommand = args.Property;

            if (obj != null)
            {
                DataGrid dataGrid = obj as DataGrid;

                if (dataGrid != null)
                {
                    if (args.OldValue != null && rowEditEndingCommand != null)
                    {
                        dataGrid.RowEditEnding -= ExecuteRowEditEndingCommand;
                    }

                    if (args.NewValue != null && rowEditEndingCommand != null)
                    {
                        dataGrid.RowEditEnding += ExecuteRowEditEndingCommand;
                    }
                }
            }
        }

        /// <summary>
        /// Called when the EditOnKeyDown property has changed.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private static void OnEditOnKeyDownChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            editOnKeyDown = args.Property;

            if (obj != null)
            {
                DataGrid dataGrid = obj as DataGrid;

                if (dataGrid != null)
                {
                    if (args.OldValue != null)
                    {
                        if ((bool)args.OldValue)
                        {
                            dataGrid.PreviewKeyDown -= OnPreviewKeyDown;
                            dataGrid.CellEditEnding -= ExecuteCellEditEndingCommand;
                        }
                    }

                    if (args.NewValue != null)
                    {
                        if ((bool)args.NewValue)
                        {
                            dataGrid.PreviewKeyDown += OnPreviewKeyDown;
                            dataGrid.CellEditEnding += ExecuteCellEditEndingCommand;
                        }
                    }
                }
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Executes the command associated to the CellEditEnding event of current data grid.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.DataGridCellEditEndingEventArgs"/> instance containing the event data.</param>
        public static void ExecuteCellEditEndingCommand(object sender, DataGridCellEditEndingEventArgs e)
        {
            FixCellBackspaceCharAndUpdateSource(e);

            DependencyObject obj = sender as DependencyObject;

            if (obj != null)
            {
                if (cellEditEndingCommand == null)
                    return;

                ICommand cmd = obj.GetValue(cellEditEndingCommand) as ICommand;

                if (cmd == null)
                    return;

                bool canExecute = true;

                canExecute = cmd.CanExecute(null);

                if (canExecute)
                    cmd.Execute(e);
            }
        }

        /// <summary>
        /// Executes the command associated to the RowEditEnding event of current data grid.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.DataGridRowEditEndingEventArgs"/> instance containing the event data.</param>
        public static void ExecuteRowEditEndingCommand(object sender, DataGridRowEditEndingEventArgs e)
        {
            DependencyObject obj = sender as DependencyObject;

            if (obj != null)
            {
                if (rowEditEndingCommand == null)
                    return;

                ICommand cmd = obj.GetValue(rowEditEndingCommand) as ICommand;

                if (cmd == null)
                    return;

                bool canExecute = true;

                canExecute = cmd.CanExecute(null);

                if (canExecute)
                    cmd.Execute(e);
            }
        }

        /// <summary>
        /// Called when the PreviewKeyDown events is raised by the data grid.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param>
        public static void OnPreviewKeyDown(object sender, KeyEventArgs e)
        {
            DependencyObject obj = sender as DependencyObject;

            if (obj != null)
            {
                bool mustEdit = (bool)obj.GetValue(editOnKeyDown);

                if (mustEdit)
                {
                    DataGrid dataGrid = obj as DataGrid;

                    if (e.SystemKey == Key.None)
                    {
                        switch (e.Key)
                        {
                            case Key.Left:
                            case Key.Right:
                            case Key.Up:
                            case Key.Down:
                            case Key.Delete:
                            case Key.LeftCtrl:
                            case Key.RightCtrl:
                            case Key.LeftAlt:
                            case Key.RightAlt:
                            case Key.Escape:
                            case Key.CapsLock:
                            case Key.Tab:
                                // Don't enter edit mode.
                                break;
                            default:
                                BeginEditCell(dataGrid);
                                break;
                        }
                    }
                }
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// DataGrid FIX: when entering editing mode pressing backspace key, the text contains a '\b' character at the beginning.
        /// See http://connect.microsoft.com/VisualStudio/feedback/details/625773/backspace-in-a-cell-in-the-wpf-datagrid-produced-b-in-the-text
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Controls.DataGridCellEditEndingEventArgs"/> instance containing the event data.</param>
        private static void FixCellBackspaceCharAndUpdateSource(DataGridCellEditEndingEventArgs e)
        {
            TextBox textBox = e.EditingElement as TextBox;
            if (textBox != null)
            {
                textBox.Text = textBox.Text.Replace("\b", String.Empty);
            }

            if (e.EditingElement != null && e.EditingElement.BindingGroup != null && e.EditingElement.BindingGroup.BindingExpressions != null)
            {
                foreach (var be in e.EditingElement.BindingGroup.BindingExpressions)
                    be.UpdateSource();
            }
        }

        /// <summary>
        /// Puts the current cell in editing mode, if its column is of type <see cref="DataGridValidableColumn"/>.
        /// </summary>
        /// <param name="dataGrid">The data grid containing the current cell to edit.</param>
        private static void BeginEditCell(DataGrid dataGrid)
        {
            DataGridCellInfo cellInfo = dataGrid.CurrentCell;

            if (cellInfo.Column is DataGridTemplateColumn)
            {
                // Test if the cell is already in edit mode.
                TextBox tb = cellInfo.Column.GetCellContent(cellInfo.Item).FindVisualChildByType<TextBox>();

                bool isEditing = tb != null;

                if (!isEditing)
                {
                    dataGrid.BeginEdit();

                    tb = cellInfo.Column.GetCellContent(cellInfo.Item).FindVisualChildByType<TextBox>();

                    if (tb != null)
                    {
                        tb.SelectAll();
                    }
                }
            }
        }

        #endregion
    }
}